#include "note.h"

/* --------------------------------------------------------------------- */
/* CLASS PITCHNOTE containing pitch note, provide conversion */
/* --------------------------------------------------------------------- */

// Constructors
PitchNote::PitchNote(QString str)
{
    // Search string list for matching result
    QRegExp exp = QRegExp ( str, Qt::CaseInsensitive );
    int index;
    if ( (index = PitchNoteList(true, true).indexOf(exp)) != -1 ) height_ = index;
    else if ( (index = PitchNoteList(true, false).indexOf(exp)) != -1 ) height_ = index;
    else if ( (index = PitchNoteList(false, true).indexOf(exp)) != -1 ) height_ = index;
    else if ( (index = PitchNoteList(false, false).indexOf(exp)) != -1 ) height_ = index;
    else {
        qWarning() << "String conversion failed (PitchNote), use default isntead." << str;
        height_ = 0;
    }
}

PitchNote::PitchNote(EquiNote eq)
{
    height_ = eq.toPitchNote().toInt();
}

// Conversion
EquiNote PitchNote::toEquiNote()
{
    if (height_ == 0) return EquiNote(0); // A
    else if (height_ == 1) return EquiNote(2); // B
    else if (height_ == 2) return EquiNote(3); // C
    else if (height_ == 3) return EquiNote(5); // D
    else if (height_ == 4) return EquiNote(7); // E
    else if (height_ == 5) return EquiNote(8); // F
    else if (height_ == 6) return EquiNote(10); // G
    else return EquiNote(0);
}

// Computation
int PitchNote::getCentsOffset(Temperament)
{
    return toEquiNote().getCentsOffset();
}

/* --------------------------------------------------------------------- */
/* CLASS ALTERNOTE containing alterations, provide conversion */
/* --------------------------------------------------------------------- */

// Constructors
AlterNote::AlterNote(QString str)
{
    // Search string list for matching result
    QRegExp exp = QRegExp ( str, Qt::CaseInsensitive );
    int index;
    if ( (index = AlterNoteList(true).indexOf(exp)) != -1 ) offset_ = index - 2;
    else if ( (index = AlterNoteList(false).indexOf(exp)) != -1 ) offset_ = index - 2;
    else {
        qWarning() << "String conversion failed (AlterNote), use default instead. Was searching for " << str;
        offset_ = 0;
    }
}

AlterNote::AlterNote(EquiNote eq)
{
    offset_ = eq.toAlterNote().toInt();
}

/* --------------------------------------------------------------------- */
/* CLASS EQUINOTE containing equivalent notes, provide conversion */
/* --------------------------------------------------------------------- */

// Constructors
EquiNote::EquiNote(QString str)
{
    // Search string list for matching result
    QRegExp exp = QRegExp ( str, Qt::CaseInsensitive );
    int index;
    if ( (index = EquiNoteList(true, true).indexOf(exp)) != -1 ) height_ = index;
    else if ( (index = EquiNoteList(true, false).indexOf(exp)) != -1 ) height_ = index;
    else if ( (index = EquiNoteList(false, true).indexOf(exp)) != -1 ) height_ = index;
    else if ( (index = EquiNoteList(false, false).indexOf(exp)) != -1 ) height_ = index;
    else {
        qWarning() << "String conversion failed (EquiNote), use default instead. Was searching for " << str;
        height_ = 0;
    }
}

// Operator
bool EquiNote::operator==(EquiNote eq)
{
   if (toPitchNote() == eq.toPitchNote() && toAlterNote() == eq.toAlterNote()) return true;
   else if (eq.getCentsOffset() == eq.getCentsOffset()) return true;
   else return false;
}

// Conversion
PitchNote EquiNote::toPitchNote()
{
    if (height_ == 0) return PitchNote(0); // A
    else if (height_ == 1) return PitchNote(1); // Bb
    else if (height_ == 2) return PitchNote(1); // B
    else if (height_ == 3) return PitchNote(2); // C
    else if (height_ == 4) return PitchNote(3); // Db
    else if (height_ == 5) return PitchNote(3); // D
    else if (height_ == 6) return PitchNote(4); // Eb
    else if (height_ == 7) return PitchNote(4); // E
    else if (height_ == 8) return PitchNote(5); // F
    else if (height_ == 9) return PitchNote(6); // Gb
    else if (height_ == 10) return PitchNote(6); // G
    else if (height_ == 11) return PitchNote(0); // Ab
    else return PitchNote(0);
}

AlterNote EquiNote::toAlterNote()
{
    if (height_ == 0) return AlterNote(0); // A
    else if (height_ == 1) return AlterNote(-1); // Bb
    else if (height_ == 2) return AlterNote(0); // B
    else if (height_ == 3) return AlterNote(0); // C
    else if (height_ == 4) return AlterNote(-1); // Db
    else if (height_ == 5) return AlterNote(0); // D
    else if (height_ == 6) return AlterNote(-1); // Eb
    else if (height_ == 7) return AlterNote(0); // E
    else if (height_ == 8) return AlterNote(0); // F
    else if (height_ == 9) return AlterNote(-1); // Gb
    else if (height_ == 10) return AlterNote(0); // G
    else if (height_ == 11) return AlterNote(-1); // Ab
    else return AlterNote(0);
}

/* --------------------------------------------------------------------- */
/* CLASS CLEF containing clef, provide conversion */
/* --------------------------------------------------------------------- */

// Constructors
Clef::Clef(QString str)
{
    // Search string list for matching result
    QRegExp exp = QRegExp ( str, Qt::CaseInsensitive );
    int index;
    if ( (index = ClefList(true).indexOf(exp)) != -1 ) clef_ = index;
    else if ( (index = ClefList(false).indexOf(exp)) != -1 ) clef_ = index;
    else {
        qWarning() << "String conversion failed (Clef), use default instead. Was searching for " << str;
        clef_ = 0;
    }
}

/* CLASS OCTAVE containing octave */
// Constructors
Octave::Octave(QString str)
{
    // Search string list for matching result
    QRegExp exp = QRegExp ( str, Qt::CaseInsensitive );
    int index;
    if ( (index = OctaveList(true).indexOf(exp)) != -1 ) oct_ = index - 1;
    else if ( (index = OctaveList(false).indexOf(exp)) != -1 ) oct_ = index - 1;
    else {
        qWarning() << "String conversion failed (Octave), use default instead. Was searching for " << str;
        oct_ = 3;
    }
}

/* --------------------------------------------------------------------- */
/* CLASS NOTE containing a full note, provide conversion */
/* --------------------------------------------------------------------- */

// Constructors
Note::Note(int offset, Note note, Temperament temp)
{
    // TODO: TO IMPLEMENT other temps
    switch (temp) {
    default:
    case Equal:
        // Retrive data
        int new_offset = note.getCentsOffset() + offset;
        // Debug
        //qDebug() << "New cents offset:" << new_offset;

        // Mathematical rounding
        if (new_offset >= 50) (new_offset += 50) /= 100;
        else if (new_offset <= -50) ((new_offset -= 50) /= 100);
        else new_offset = 0;

        // Debug
        //qDebug() << "Reduced offset:" << new_offset;
        int octave, equiv_class;

        //new_offset += 8; // C offset
        // Avoid stupid C++ floor rounding
        //if (new_offset + 8 >= 0) octave = (new_offset + 9) / 12 + 4;
        //else octave = (new_offset + 9) / 12 + 3;
        octave = MathDivide(new_offset + 9, 12) + 4;

        // Avoid negative class new_offset % 12
        equiv_class = MathReminder(new_offset, 12);

        // Debug
        // qDebug() << "Is calculated to be on octave" << octave << "with class" << equiv_class;
        // Construct the note using equivclass
        *this = Note(EquiNote(equiv_class), Octave(octave));

        break;
    }
}

Note::Note(qreal freq, qreal base, Note note, Temperament temp)
{
    int diff = centsDiff(base, freq);
    *this = Note(diff, note, temp);
}

// Operators
bool Note::operator==(Note note)
{
   // DEBUG operator
   //qDebug() << "Comparing note:" << toQString() << "converted" << toEquiNote().toQString() << toOctave().toQString() << "offset" << getCentsOffset();
   //qDebug() << "To note:" << note.toQString() << "converted" << note.toEquiNote().toQString() << note.toOctave().toQString() << "offset" << note.getCentsOffset();

   if (getCentsOffset() == note.getCentsOffset()) return true;
   return false;
}

// Conversion
EquiNote Note::toEquiNote()
{
    return EquiNote(toPitchNote(), toAlterNote());
}

// Computation
QString Note::getPathToImage(Clef clf, QString basename)
{
    QString spacer = "_";
    QString path = "/images/notes/" + clf.toQString(false) + "/";
    QString filename = pitch_.toQString(false) + spacer + alter_.toQString(false) + spacer + oct_.toQString(false);
    QString ext = ".png";
    return basename + path + filename + ext;
}

QPixmap Note::getQPixmap(Clef clf)
{
    return QPixmap(getPathToImage(clf, ":"));
}

// Computations
// Return cents of the note above (below) A Natural C4 (Default NOTE))
int Note::getCentsOffset(Temperament temp)
{
    // Warning here loop notes
    return toPitchNote().getCentsOffset() + toAlterNote().getCentsOffset() + oct_.getCentsOffset();
}

int Note::getCentDiff(Note fromNote, Temperament temp)
{
    //qDebug() << "This note offset:" << getCentsOffset();
    //qDebug() << "Other note offset:" << fromNote.getCentsOffset();
    //qDebug() << "This note: " << toQString();
    //qDebug() << "Equinote conversion:" << toPitchNote().toInt();
    //qDebug() << "Equinote conversion:" << toAlterNote().toInt();
    //qDebug() << "Equinote conversion:" << toEquiNote().toInt();
    //qDebug() << "Other note: " << fromNote.toQString();
    return getCentsOffset() - fromNote.getCentsOffset();
}

// Return frequency over give frequency of the base note, can use different temperament
// TODO: implement different temperament
qreal Note::getFreq(qreal baseFreq, Note baseNote, Temperament temp)
{
    int centDiff = getCentDiff(baseNote);
    //qDebug() << "Cents difference:" << getCentDiff(baseNote);
    //qDebug() << "Using base freq " << baseFreq << " and base note" << baseNote.toQString();
    return baseFreq * getRatioFreq(centDiff, temp);

}

// Transform this in base frombase to the a new note on base tobase
// Leave away tobase to get real note
Note Note::transpose(Note frombase, Note tobase)
{
    //qDebug() << "+++++BEGIN CONVERSION+++++++++";
    //qDebug() << "Transforming:" << toQString();
    //qDebug() << "From base:" << frombase.toQString();
    //qDebug() << "To base:" << tobase.toQString();
    //qDebug() << "Cent diff from this to from:" << getCentDiff(frombase);
    //qDebug() << "New note will be:" << Note(getCentDiff(frombase), tobase).toQString();
    return Note(getCentDiff(frombase), tobase);
}
